route.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. "use server";
  2. import { getServerSideConfig } from "@/app/config/server";
  3. import {
  4. TENCENT_BASE_URL,
  5. ApiPath,
  6. ModelProvider,
  7. ServiceProvider,
  8. Tencent,
  9. } from "@/app/constant";
  10. import { prettyObject } from "@/app/utils/format";
  11. import { NextRequest, NextResponse } from "next/server";
  12. import { auth } from "@/app/api/auth";
  13. import { isModelAvailableInServer } from "@/app/utils/model";
  14. import * as crypto from "node:crypto";
  15. const serverConfig = getServerSideConfig();
  16. async function handle(
  17. req: NextRequest,
  18. { params }: { params: { path: string[] } },
  19. ) {
  20. console.log("[Tencent Route] params ", params);
  21. if (req.method === "OPTIONS") {
  22. return NextResponse.json({ body: "OK" }, { status: 200 });
  23. }
  24. const authResult = auth(req, ModelProvider.Hunyuan);
  25. if (authResult.error) {
  26. return NextResponse.json(authResult, {
  27. status: 401,
  28. });
  29. }
  30. try {
  31. const response = await request(req);
  32. return response;
  33. } catch (e) {
  34. console.error("[Tencent] ", e);
  35. return NextResponse.json(prettyObject(e));
  36. }
  37. }
  38. export const GET = handle;
  39. export const POST = handle;
  40. async function request(req: NextRequest) {
  41. const controller = new AbortController();
  42. // tencent just use base url or just remove the path
  43. let path = `${req.nextUrl.pathname}`.replaceAll(
  44. ApiPath.Tencent + "/" + Tencent.ChatPath,
  45. "",
  46. );
  47. let baseUrl = serverConfig.tencentUrl || TENCENT_BASE_URL;
  48. if (!baseUrl.startsWith("http")) {
  49. baseUrl = `https://${baseUrl}`;
  50. }
  51. if (baseUrl.endsWith("/")) {
  52. baseUrl = baseUrl.slice(0, -1);
  53. }
  54. console.log("[Proxy] ", path);
  55. console.log("[Base Url]", baseUrl);
  56. const timeoutId = setTimeout(
  57. () => {
  58. controller.abort();
  59. },
  60. 10 * 60 * 1000,
  61. );
  62. const fetchUrl = `${baseUrl}${path}`;
  63. const body = await req.text();
  64. const fetchOptions: RequestInit = {
  65. headers: {
  66. ...getHeader(body),
  67. },
  68. method: req.method,
  69. body,
  70. redirect: "manual",
  71. // @ts-ignore
  72. duplex: "half",
  73. signal: controller.signal,
  74. };
  75. try {
  76. const res = await fetch(fetchUrl, fetchOptions);
  77. // to prevent browser prompt for credentials
  78. const newHeaders = new Headers(res.headers);
  79. newHeaders.delete("www-authenticate");
  80. // to disable nginx buffering
  81. newHeaders.set("X-Accel-Buffering", "no");
  82. return new Response(res.body, {
  83. status: res.status,
  84. statusText: res.statusText,
  85. headers: newHeaders,
  86. });
  87. } finally {
  88. clearTimeout(timeoutId);
  89. }
  90. }
  91. // 使用 SHA-256 和 secret 进行 HMAC 加密
  92. function sha256(message: any, secret = "", encoding?: string) {
  93. return crypto.createHmac("sha256", secret).update(message).digest(encoding);
  94. }
  95. // 使用 SHA-256 进行哈希
  96. function getHash(message: any, encoding = "hex") {
  97. return crypto.createHash("sha256").update(message).digest(encoding);
  98. }
  99. function getDate(timestamp: number) {
  100. const date = new Date(timestamp * 1000);
  101. const year = date.getUTCFullYear();
  102. const month = ("0" + (date.getUTCMonth() + 1)).slice(-2);
  103. const day = ("0" + date.getUTCDate()).slice(-2);
  104. return `${year}-${month}-${day}`;
  105. }
  106. function getHeader(payload: any) {
  107. // https://cloud.tencent.com/document/api/1729/105701
  108. // 密钥参数
  109. const SECRET_ID = serverConfig.tencentSecretId;
  110. const SECRET_KEY = serverConfig.tencentSecretKey;
  111. const endpoint = "hunyuan.tencentcloudapi.com";
  112. const service = "hunyuan";
  113. const region = ""; // optional
  114. const action = "ChatCompletions";
  115. const version = "2023-09-01";
  116. const timestamp = Math.floor(Date.now() / 1000);
  117. //时间处理, 获取世界时间日期
  118. const date = getDate(timestamp);
  119. // ************* 步骤 1:拼接规范请求串 *************
  120. const hashedRequestPayload = getHash(payload);
  121. const httpRequestMethod = "POST";
  122. const contentType = "application/json";
  123. const canonicalUri = "/";
  124. const canonicalQueryString = "";
  125. const canonicalHeaders =
  126. `content-type:${contentType}\n` +
  127. "host:" +
  128. endpoint +
  129. "\n" +
  130. "x-tc-action:" +
  131. action.toLowerCase() +
  132. "\n";
  133. const signedHeaders = "content-type;host;x-tc-action";
  134. const canonicalRequest = [
  135. httpRequestMethod,
  136. canonicalUri,
  137. canonicalQueryString,
  138. canonicalHeaders,
  139. signedHeaders,
  140. hashedRequestPayload,
  141. ].join("\n");
  142. // ************* 步骤 2:拼接待签名字符串 *************
  143. const algorithm = "TC3-HMAC-SHA256";
  144. const hashedCanonicalRequest = getHash(canonicalRequest);
  145. const credentialScope = date + "/" + service + "/" + "tc3_request";
  146. const stringToSign =
  147. algorithm +
  148. "\n" +
  149. timestamp +
  150. "\n" +
  151. credentialScope +
  152. "\n" +
  153. hashedCanonicalRequest;
  154. // ************* 步骤 3:计算签名 *************
  155. const kDate = sha256(date, "TC3" + SECRET_KEY);
  156. const kService = sha256(service, kDate);
  157. const kSigning = sha256("tc3_request", kService);
  158. const signature = sha256(stringToSign, kSigning, "hex");
  159. // ************* 步骤 4:拼接 Authorization *************
  160. const authorization =
  161. algorithm +
  162. " " +
  163. "Credential=" +
  164. SECRET_ID +
  165. "/" +
  166. credentialScope +
  167. ", " +
  168. "SignedHeaders=" +
  169. signedHeaders +
  170. ", " +
  171. "Signature=" +
  172. signature;
  173. return {
  174. Authorization: authorization,
  175. "Content-Type": contentType,
  176. Host: endpoint,
  177. "X-TC-Action": action,
  178. "X-TC-Timestamp": timestamp.toString(),
  179. "X-TC-Version": version,
  180. "X-TC-Region": region,
  181. };
  182. }